import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.jgrapht.alg.NeighborIndex;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.SimpleWeightedGraph;


/**
 * Iteratively goes over all un-clustered, annotated genes, checks if they have a neigboring cluster
 * that is annotated with one of their annotations, and adds them to that cluster if exists.
 * @author odedmagg
 *
 */
public class CLMAddUnclusteredNeighbours implements ClusterListManipulator {
	private HashMap<String, Gene> _geneMap;
	private AnnotationsManager _annotMgr;
	private SimpleWeightedGraph<String, DefaultWeightedEdge> _graph;

	public CLMAddUnclusteredNeighbours(HashMap<String, Gene> geneMap,
			AnnotationsManager annotMgr,
			SimpleWeightedGraph<String, DefaultWeightedEdge> graph) {
		_geneMap = geneMap;
		_annotMgr = annotMgr;
		_graph      = graph;
	}
	
	public void manipulate(List<Cluster> clusters) {
		// Sanity check
		if ((_graph == null) || (_annotMgr == null) || (_geneMap == null))
			return;
		// Add all non-clustered, annotated genes to the queue.
		BlockingQueue<Gene> q = new LinkedBlockingQueue<Gene>();
		for (Gene g: _geneMap.values()) {
			if ((g.isAnnotated()) && (g.clustersCount() == 0)) {
				q.offer(g);
			}
		}
		
		// Create a neighbours cache
		NeighborIndex<String, DefaultWeightedEdge> neighborsIndex = new NeighborIndex<String, DefaultWeightedEdge>(_graph);
		
		Gene g;
		while (!q.isEmpty()) {
			g = q.poll();
			
			Set<String> neighbors = neighborsIndex.neighborsOf(g.getName());
			for (String neighbourName: neighbors) { 				
				Gene neighbor = _geneMap.get(neighbourName);
				assert (neighbor != null); // We expect the geneMap and the graph to be synchronized...
				// Check if the neighbor belongs to a cluster
				if (neighbor.clustersCount() > 0) {
					// Consider adding current Gene to the neighbor's cluster.
					Cluster cluster = neighbor.clustersIterator().next();
					if (_annotMgr.isGeneAnnotated(g, cluster.getAnnotation())) {
						// Add gene to the cluster!
						cluster.add(g);
						
						// Send unclustered, annotated neighbors to the back of the queue - perhaps now they can be
						// added to the new cluster.
						// Note that genes that are already in the queue are not re-added.
						for (String neighbourName2: neighbors) {
							neighbor = _geneMap.get(neighbourName2);
							assert(neighbor == null);
							if ((neighbor.clustersCount() == 0) && (neighbor.isAnnotated()) && (!q.contains(neighbor))) {
								q.offer(neighbor);
							}
						}
						
						break; // No need to look for more clusters to add current gene to...
					}
				}
			}
		}
	}


	public void setGeneMap(HashMap<String, Gene> _geneMap) {
		this._geneMap = _geneMap;
	}


	public HashMap<String, Gene> getGeneMap() {
		return _geneMap;
	}


	public void setAnnotMgr(AnnotationsManager _annotMgr) {
		this._annotMgr = _annotMgr;
	}


	public AnnotationsManager getAnnotMgr() {
		return _annotMgr;
	}


	public void setGraph(SimpleWeightedGraph<String, DefaultWeightedEdge> _graph) {
		this._graph = _graph;
	}


	public SimpleWeightedGraph<String, DefaultWeightedEdge> getGraph() {
		return _graph;
	}
}
